#include <string.h>     /* strerror */
#include <stdlib.h>     /* memset() */
#include <errno.h>      /* errno */
#include <math.h>       /* fabs() */
#include <sys/socket.h> /* send(), recv(), socket(), accept(),
				   bind(), listen() */
#include <sys/time.h>   /* struct timeval */
#include "sendn.h"      /* sendn() */
#include "rcs_print.hh" /* rcs_print_error() */
#include "_timer.h"     /* etime(), esleep() */

int sendn_timedout = 0;
int print_sendn_timeout_errors = 1;

/* Write "n" bytes to a descriptor. */
int sendn(int fd, const void* vptr, int n, int _flags, double _timeout)
{
    int nleft;
    long nwritten;
    int select_ret;
    double start_time;
    char* ptr;
    struct timeval timeout_tv;
    fd_set send_fd_set;

    timeout_tv.tv_sec = (long)_timeout;
    timeout_tv.tv_usec = (long)(_timeout * 1000000.0);
    if (timeout_tv.tv_usec >= 1000000)
    {
        timeout_tv.tv_usec = timeout_tv.tv_usec % 1000000;
    }
    FD_ZERO(&send_fd_set);
    FD_SET(fd, &send_fd_set);

    ptr = (char*)vptr; /* can't do pointer arithmetic on void* */
    nleft = n;
    start_time = etime();
    while (nleft > 0)
    {
        if (fabs(_timeout) > 1E-6)
        {
            if (_timeout > 0)
            {
                double timeleft;
                timeleft = start_time + _timeout - etime();
                if (timeleft <= 0.0)
                {
                    if (print_sendn_timeout_errors)
                    {
                        rcs_print_error("sendn(fd=%d, vptr=%p, int n=%d, int flags=%d, double _timeout=%f) timed out.\n", fd, vptr, n, _flags, _timeout);
                    }
                    sendn_timedout = 1;
                    return -1;
                }
                timeout_tv.tv_sec = (long)timeleft;
                timeout_tv.tv_usec = (long)(timeleft * 1000000.0);
                if (timeout_tv.tv_usec >= 1000000)
                {
                    timeout_tv.tv_usec = timeout_tv.tv_usec % 1000000;
                }
                select_ret = select(fd + 1, (fd_set*)NULL, &send_fd_set, (fd_set*)NULL, &timeout_tv);
            }
            else
            {
                select_ret = select(fd + 1, (fd_set*)NULL, &send_fd_set, (fd_set*)NULL, NULL);
            }
            switch (select_ret)
            {
            case -1:
                rcs_print_error("Error in select: %d -> %s\n", errno, strerror(errno));
                rcs_print_error("sendn(fd=%d, vptr=%p, int n=%d, int _flags=%d, double _timeout=%f) failed.\n", fd, vptr, n, _flags, _timeout);
                return -1;

            case 0:
                rcs_print_error("sendn(fd=%d, vptr=%p, int n=%d, int _flags=%d, double _timeout=%f) timed out.\n", fd, vptr, n, _flags, _timeout);
                return -1;

            default:
                break;
            }
        }
        if ((nwritten = send(fd, ptr, nleft, _flags)) == -1)
        {
            rcs_print_error("Send error: %d = %s\n", errno, strerror(errno));
            return (-1); /* error */
        }
        nleft -= nwritten;
        ptr += nwritten;
        if (nleft > 0 && _timeout > 0.0)
        {
            double duration;
            duration = etime() - start_time;
            if (duration > _timeout)
            {
                rcs_print_error("sendn: timed out after %f seconds.\n", duration);
                return (-1);
            }
            esleep(0.001);
        }
    }
    rcs_print_debug(PRINT_SOCKET_WRITE_SIZE, "wrote %d bytes to %d\n", n, fd);
    return (n);
}
